package flexo.graphic.entity;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import org.lwjgl.opengl.GL11;

import flexo.math.Random;
import flexo.math.Vector;
import flexo.scene.Node;
import flexo.graphic.Color;
import flexo.graphic.FrameListener;
import flexo.graphic.entity.particle.*;

public class ParticleSystem extends DynamicMesh implements FrameListener
{	
	protected Random generator;
	protected float defaultSize;
	protected float maxLife;
	protected int maxParticles;
	protected float particlesPerSecond;
	
	protected List<ParticleAffector> affectorList;
	protected List<Particle> particleList;
	
	protected Node world;
	
	protected boolean updateParticles;
	protected boolean updateGeometry;
	
	protected Color color;
	
	public ParticleSystem(int particles, float life)
	{
		flexo.Kernel.log("flexo.graphic.entity.ParticleSystem", "Creating particle system");
		defaultSize = 0.4f;
		maxLife = life;
		maxParticles = particles;
		
		particlesPerSecond = particles / life;
		
		world = null;
		
		generator = new Random();		

		// Each particles uses 4 vertices and 6 indices (a square)
		init(maxParticles * 4, maxParticles * 6, flexo.graphic.Geometry.UPDATE_ALWAYS);
		
		// Initialize indices
		int j = 0;
		for (int i = 0; i < numIndices; i += 6)
		{
			this.setIndex(i, j);
			this.setIndex(i+1, j+1);
			this.setIndex(i+2, j+2);
			this.setIndex(i+3, j);
			this.setIndex(i+4, j+2);
			this.setIndex(i+5, j+3);
			j += 4;
		}
		
		// Initialize vertices
		for (int i = 0; i < maxParticles; ++i)
		{
			this.setVertexTexCoord((i*4),0,1);
			this.setVertexTexCoord((i*4)+1,0,0);
			this.setVertexTexCoord((i*4)+2,1,0);
			this.setVertexTexCoord((i*4)+3,1,1);
		}
		
		particleList = new ArrayList<Particle>(maxParticles);		
		for (int i = 0; i < maxParticles; ++i)
		{
			particleList.add(new Particle(this));
		}
		
		color = new Color(1,1,1,1);
		
		affectorList = new LinkedList<ParticleAffector>();
		flexo.graphic.GraphicSystem gs = (flexo.graphic.GraphicSystem)flexo.Kernel.getSystem(flexo.graphic.GraphicSystem.ID);
		gs.addFrameListener(this);
	}
	
	@ Override
	public void remove()
	{
		flexo.graphic.GraphicSystem gs = (flexo.graphic.GraphicSystem)flexo.Kernel.getSystem(flexo.graphic.GraphicSystem.ID);
		gs.removeFrameListener(this);
		super.remove();
	}
	
	public Color getColor()
	{
		return color;
	}
	
	public void setColor(Color c)
	{
		color.set(c);
		color.setAlpha(1);
	}
	
	public void setWorldNode(Node world)
	{
		this.world = world;
	}
	
	public float getMaxLife()
	{
		return maxLife;
	}
	
	public float getRandom()
	{
		return generator.nextFloat();
	}
	
	public void addAffector(ParticleAffector aff)
	{
		affectorList.add(aff);
	}
	
	public void removeAffector(ParticleAffector aff)
	{
		affectorList.remove(aff);
	}
	
	public void setSize(float size)
	{
		if (size > 0)
		{
			this.defaultSize = size * 0.5f;
		}
	}
	
	public Node getWorldNode()
	{
		return world;
	}
	
	public boolean isStatic()
	{
		return world == null;
	}
	
	public float getDefaultSize()
	{
		return this.defaultSize;
	}
	
	protected void updateParticles()
	{
		if (updateParticles)
		{
			updateParticles = false;
			
			flexo.graphic.GraphicSystem gs = (flexo.graphic.GraphicSystem)flexo.Kernel.getSystem(flexo.graphic.GraphicSystem.ID);
			float elapsed = gs.getElapsedTime();
			
			// for each particle
			for (Particle p : particleList)
			{
				// Update it's state	
				p.update(elapsed);			
					
				// Affect the particle
				for (ParticleAffector pa: affectorList)
				{
					pa.affect(p);
				}
			}
		}
	}
	
	protected void updateGeometry()
	{
		if (updateGeometry)
		{
			updateGeometry = false;
			
			Vector forward = flexo.graphic.Camera.getRenderingCamera().getForwardVector();
			Vector right = flexo.graphic.Camera.getRenderingCamera().getRightVector();
			Vector up = Vector.getTempVector();
			Vector.cross(right, forward, up);				
			
			// for each particle
			int pos = 0;
			for (Particle p : particleList)
			{				
				Vector pPos = p.getPosition();
								
				// Update the geometry
				
				// 1st Vertex
				setVertexPosition((4 * pos), 
						pPos.x - right.x*p.getSize() + up.x*p.getSize(),
						pPos.y - right.y*p.getSize() + up.y*p.getSize(),
						pPos.z - right.z*p.getSize() + up.z*p.getSize());
				setVertexColor((4 * pos), p.getColor().getRed(), p.getColor().getGreen()
						, p.getColor().getBlue(), p.getColor().getAlpha());
				
				// 2st Vertex
				setVertexPosition((4 * pos)+1, 
						pPos.x - right.x*p.getSize() - up.x*p.getSize(),
						pPos.y - right.y*p.getSize() - up.y*p.getSize(),
						pPos.z - right.z*p.getSize() - up.z*p.getSize());
				setVertexColor((4 * pos)+1, p.getColor().getRed(), p.getColor().getGreen()
						, p.getColor().getBlue(), p.getColor().getAlpha());
				
				// 3st Vertex
				setVertexPosition((4 * pos)+2, 
						pPos.x + right.x*p.getSize() - up.x*p.getSize(),
						pPos.y + right.y*p.getSize() - up.y*p.getSize(),
						pPos.z + right.z*p.getSize() - up.z*p.getSize());
				setVertexColor((4 * pos)+2, p.getColor().getRed(), p.getColor().getGreen()
						, p.getColor().getBlue(), p.getColor().getAlpha());
				
				// 4st Vertex
				setVertexPosition((4 * pos)+3, 
						pPos.x + right.x*p.getSize() + up.x*p.getSize(),
						pPos.y + right.y*p.getSize() + up.y*p.getSize(),
						pPos.z + right.z*p.getSize() + up.z*p.getSize());
				setVertexColor((4 * pos)+3, p.getColor().getRed(), p.getColor().getGreen()
						, p.getColor().getBlue(), p.getColor().getAlpha());
				
				++pos;
			}
		}
	}
	
	public void update()
	{		
		updateParticles();
		updateGeometry();
		
		if (!isStatic())
		{
			GL11.glPopMatrix();
			GL11.glPushMatrix();
		}
		
		// Update the mesh itself
		super.update();
	}

	@Override
	public void onFrameStart()
	{
		updateParticles = true;
	}

	@Override
	public void onCameraRender()
	{
		updateGeometry = true;
	}
}
